"""
DeepGP Streamlit Web Application
用于糖肽 MS/MS 谱图和保留时间预测的图形界面
"""

import streamlit as st
import os
import sys
import subprocess
import pandas as pd
from pathlib import Path
import json
import time

# 页面配置
st.set_page_config(
    page_title="DeepGP - 糖肽谱图与保留时间预测",
    page_icon="🔬",
    layout="wide",
    initial_sidebar_state="expanded"
)

# 自定义CSS
st.markdown("""
<style>
    .main-header {
        font-size: 2.5rem;
        color: #1f77b4;
        text-align: center;
        margin-bottom: 2rem;
    }
    .sub-header {
        font-size: 1.5rem;
        color: #ff7f0e;
        margin-top: 1rem;
        margin-bottom: 1rem;
    }
    .success-box {
        padding: 1rem;
        border-radius: 0.5rem;
        background-color: #d4edda;
        border: 1px solid #c3e6cb;
        color: #155724;
    }
    .error-box {
        padding: 1rem;
        border-radius: 0.5rem;
        background-color: #f8d7da;
        border: 1px solid #f5c6cb;
        color: #721c24;
    }
    .info-box {
        padding: 1rem;
        border-radius: 0.5rem;
        background-color: #d1ecf1;
        border: 1px solid #bee5eb;
        color: #0c5460;
    }
</style>
""", unsafe_allow_html=True)


def normalize_path(path):
    """规范化路径，确保末尾有 /"""
    if not path:
        return path
    return path.rstrip('/') + '/'


def run_command(cmd, description=""):
    """执行命令行命令并显示进度"""
    st.info(f"🔄 正在执行: {description}")
    
    progress_bar = st.progress(0)
    status_text = st.empty()
    
    try:
        # 调整命令路径（因为核心代码在 deepgp/ 目录）
        if cmd.startswith("python "):
            cmd = cmd.replace("python ", "python deepgp/", 1)
        
        # 显示命令
        with st.expander("查看执行的命令"):
            st.code(cmd, language="bash")
        
        # 执行命令（从项目根目录执行）
        project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
        process = subprocess.Popen(
            cmd,
            shell=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
            cwd=project_root
        )
        
        # 模拟进度（实际进度难以准确获取）
        for i in range(100):
            time.sleep(0.05)
            progress_bar.progress(i + 1)
            if process.poll() is not None:
                break
        
        stdout, stderr = process.communicate()
        
        if process.returncode == 0:
            st.success(f"✅ {description} 完成！")
            with st.expander("查看输出"):
                st.text(stdout)
            return True, stdout
        else:
            st.error(f"❌ {description} 失败！")
            with st.expander("查看错误信息"):
                st.text(stderr)
            return False, stderr
            
    except Exception as e:
        st.error(f"❌ 执行失败: {str(e)}")
        return False, str(e)


def main():
    """主函数"""
    
    # 侧边栏 - 功能选择
    st.sidebar.markdown("# 🔬 DeepGP")
    st.sidebar.markdown("---")
    
    page = st.sidebar.radio(
        "选择功能",
        [
            "🏠 主页",
            "📊 数据预处理",
            "🏋️ MS/MS 模型训练",
            "🔬 MS/MS 谱图预测",
            "🏋️ 保留时间模型训练",
            "🎯 保留时间预测",
            "⏱️ 保留时间校准",
            "📈 结果查看",
            "ℹ️ 帮助文档"
        ]
    )
    
    st.sidebar.markdown("---")
    st.sidebar.markdown("### 关于")
    st.sidebar.info(
        "DeepGP 是一个基于深度学习的糖肽 MS/MS 谱图和保留时间预测框架。\n\n"
        "📧 liang_qiao@fudan.edu.cn"
    )
    
    # 主页面内容
    if page == "🏠 主页":
        show_home()
    elif page == "📊 数据预处理":
        show_preprocessing()
    elif page == "🏋️ MS/MS 模型训练":
        show_ms2_training()
    elif page == "🔬 MS/MS 谱图预测":
        show_ms2_prediction()
    elif page == "🏋️ 保留时间模型训练":
        show_rt_training()
    elif page == "🎯 保留时间预测":
        show_rt_prediction()
    elif page == "⏱️ 保留时间校准":
        show_rt_calibration()
    elif page == "📈 结果查看":
        show_results()
    elif page == "ℹ️ 帮助文档":
        show_help()


def show_home():
    """显示主页"""
    st.markdown('<div class="main-header">🔬 DeepGP</div>', unsafe_allow_html=True)
    st.markdown('<div style="text-align: center; font-size: 1.2rem; color: #666;">糖肽 MS/MS 谱图与保留时间预测平台</div>', unsafe_allow_html=True)
    
    st.markdown("---")
    
    col1, col2, col3 = st.columns(3)
    
    with col1:
        st.markdown("### 📊 数据预处理")
        st.write("将 pGlyco3 鉴定结果和 MGF 文件转换为模型输入格式")
        st.button("开始预处理 →", key="goto_preprocess")
    
    with col2:
        st.markdown("### 🔬 谱图预测")
        st.write("使用深度学习模型预测糖肽的 MS/MS 质谱图")
        st.button("开始预测 →", key="goto_predict")
    
    with col3:
        st.markdown("### ⏱️ 保留时间")
        st.write("校准和预测糖肽的保留时间")
        st.button("开始分析 →", key="goto_rt")
    
    st.markdown("---")
    
    # 快速开始指南
    st.markdown("## 🚀 快速开始")
    
    with st.expander("📖 第一次使用？点击查看快速开始指南"):
        st.markdown("""
        ### Demo 快速体验（推荐）
        
        **所有默认值已填好，直接点 🚀 按钮即可！**
        
        1. **📊 数据预处理** → 点 🚀 开始预处理
           - 自动处理 demo 数据
           - 输出: `demo_output.csv`
        
        2. **🏋️ MS/MS 模型训练** → 点 🚀 开始训练  
           - 训练 MS/MS 预测模型
           - 模型保存到: `deepgp/output/demo_ms2_training/`
        
        3. **🔬 MS/MS 谱图预测** → 点 🚀 开始预测
           - 使用训练好的模型预测
           - 更新模型路径为上一步的输出
        
        4. **🏋️ 保留时间模型训练** → 点 🚀 开始训练
           - 训练 RT 预测模型
           - 模型保存到: `deepgp/output/demo_rt_training/`
        
        5. **🎯 保留时间预测** → 点 🚀 开始预测
           - 使用训练好的模型预测
           - 更新模型路径为上一步的输出
        
        ---
        
        ### 使用自己的数据
        
        1. **数据预处理** 📊
           - 准备 pGlyco3 鉴定结果（.txt）
           - 准备 MGF 质谱数据
           - 修改路径后处理
        
        2. **模型训练** 🏋️
           - 建议数据量 > 1000 条
           - 训练时间：数小时到数天
        
        3. **进行预测** 🎯
           - 使用训练好的模型
           - 或下载预训练模型
        
        ### 需要帮助？
        - 查看侧边栏的"ℹ️ 帮助文档"
        - 访问 [GitHub](https://github.com/lmsac/DeepGP)
        - 发送邮件至 liang_qiao@fudan.edu.cn
        """)
    
    # 系统状态
    st.markdown("## 📊 系统状态")
    
    col1, col2, col3 = st.columns(3)
    
    with col1:
        st.metric("Python 版本", f"{sys.version_info.major}.{sys.version_info.minor}")
    
    with col2:
        try:
            import torch
            gpu_available = torch.cuda.is_available()
            gpu_text = f"可用 ({torch.cuda.device_count()} 个)" if gpu_available else "不可用"
            st.metric("GPU 状态", gpu_text)
        except:
            st.metric("GPU 状态", "未知")
    
    with col3:
        project_dir = os.path.dirname(os.path.abspath(__file__))
        st.metric("项目目录", "已配置" if os.path.exists(project_dir) else "未配置")
    
    # 最近更新
    st.markdown("---")
    st.markdown("## 📰 更新日志")
    st.info("""
    **版本 1.0** (当前版本)
    - ✨ 发布 Web 应用界面
    - 📊 支持数据预处理
    - 🔬 支持 MS/MS 谱图预测
    - ⏱️ 支持保留时间预测
    - 📈 结果可视化和分析
    """)


def show_preprocessing():
    """数据预处理页面"""
    st.markdown('<div class="sub-header">📊 数据预处理</div>', unsafe_allow_html=True)
    
    st.write("将 pGlyco3 鉴定结果和 MGF 文件转换为模型输入格式")
    
    # 参数输入
    with st.form("preprocessing_form"):
        st.markdown("### 输入参数")
        
        col1, col2 = st.columns(2)
        
        with col1:
            datafold = st.text_input(
                "数据目录路径 *",
                value="deepgp/demo_data/human/demo/",
                help="包含鉴定结果和 MGF 文件的目录"
            )
            
            dfname = st.text_input(
                "鉴定结果文件名 *",
                value="pGlycoDB-GP-FDR-Pro_demo.txt",
                help="pGlyco3 输出的鉴定结果文件"
            )
            
            mgfdatafold = st.text_input(
                "MGF 文件夹名 *",
                value="MSConvert_mgf_demo/",
                help="存放 MGF 文件的文件夹名称"
            )
        
        with col2:
            output_name = st.text_input(
                "输出文件名 *",
                value="demo_output.csv",
                help="处理后的输出文件名"
            )
            
            dup = st.selectbox(
                "重复处理方式",
                ["Drop_duplicated", "Duplicated", "Retained_all"],
                index=0,
                help="Drop_duplicated: 保留一个 | Duplicated: 只保留重复的 | Retained_all: 保留所有"
            )
            
            mgfsourceorign = st.selectbox(
                "MGF 文件来源",
                ["MsConvert", "pGlyco3"],
                index=0,
                help="生成 MGF 文件的工具"
            )
        
        submitted = st.form_submit_button("🚀 开始预处理", use_container_width=True)
    
    if submitted:
        # 验证输入
        if not all([datafold, dfname, mgfdatafold, output_name]):
            st.error("❌ 请填写所有必填字段（标记 * 的字段）")
            return
        
        # 规范化路径（确保末尾有 /）
        datafold = normalize_path(datafold)
        mgfdatafold = normalize_path(mgfdatafold)
        
        # 检查路径是否存在
        if not os.path.exists(datafold):
            st.error(f"❌ 数据目录不存在: {datafold}")
            return
        
        # 构建命令
        cmd = f"""python 1_dataset_format.py \
            --datafold {datafold} \
            --dfname {dfname} \
            --mgfdatafold {mgfdatafold} \
            --output_name {output_name} \
            --dup {dup} \
            --mgfsourceorign {mgfsourceorign}"""
        
        # 执行命令
        success, output = run_command(cmd, "数据预处理")
        
        if success:
            output_file = os.path.join(datafold, output_name)
            if os.path.exists(output_file):
                st.markdown("### 📄 预处理结果")
                
                # 显示文件信息
                file_size = os.path.getsize(output_file) / 1024 / 1024  # MB
                st.info(f"输出文件: {output_file}\n文件大小: {file_size:.2f} MB")
                
                # 预览数据
                try:
                    df = pd.read_csv(output_file, nrows=10)
                    st.write(f"数据预览（前10行，共 {len(pd.read_csv(output_file))} 行）：")
                    st.dataframe(df)
                    
                    # 统计信息
                    col1, col2, col3 = st.columns(3)
                    full_df = pd.read_csv(output_file)
                    with col1:
                        st.metric("总行数", len(full_df))
                    with col2:
                        st.metric("列数", len(full_df.columns))
                    with col3:
                        if 'iden_pep' in full_df.columns:
                            st.metric("唯一糖肽", full_df['iden_pep'].nunique())
                except Exception as e:
                    st.warning(f"⚠️ 无法预览数据: {str(e)}")
    
    # 使用说明
    with st.expander("ℹ️ 使用说明"):
        st.markdown("""
        ### 输入要求
        
        1. **数据目录**: 包含以下文件的目录
           - pGlyco3 鉴定结果文件
           - MGF 文件夹（内含一个或多个 .mgf 文件）
        
        2. **文件格式**:
           - 鉴定结果: pGlyco3 输出的 tab 分隔文本文件
           - MGF 文件: 使用 MsConvert 或 pGlyco3 生成
        
        ### 输出
        
        - 生成 CSV 文件，包含：
          - 糖肽序列信息
          - 谱图数据
          - 鉴定信息
          - 后续分析所需的其他字段
        
        ### 注意事项
        
        - 确保路径使用绝对路径
        - MGF 文件可以有多个，会自动合并处理
        - 处理大数据集可能需要较长时间
        """)


def show_ms2_prediction():
    """MS/MS 谱图预测页面"""
    st.markdown('<div class="sub-header">🔬 MS/MS 谱图预测</div>', unsafe_allow_html=True)
    
    st.write("使用训练好的模型预测糖肽的 MS/MS 质谱图")
    
    # 提示如何找到模型文件
    st.info("💡 **如何找到训练好的模型**：\n"
            "- 模型保存在: `数据目录/train_combine_byBYprocessed/checkpoints/[时间戳]/epoch-xxx.pt`\n"
            "- 例如: `deepgp/demo_data/human/demo/train_combine_byBYprocessed/checkpoints/2025-11-08-07-36-59-290604/epoch-150_step-150_mediancos-0.770331.pt`\n"
            "- 或在服务器上运行: `find deepgp/demo_data/ -name 'epoch-*.pt' -type f`")
    
    # 参数输入
    with st.form("ms2_prediction_form"):
        st.markdown("### 输入参数")
        
        col1, col2 = st.columns(2)
        
        with col1:
            trainpathcsv = st.text_input(
                "测试数据文件 *",
                value="deepgp/demo_data/human/demo/demo_output.csv",
                help="预处理后的 CSV 文件（使用第1步的输出）"
            )
            
            datafold = st.text_input(
                "输出目录 *",
                value="deepgp/demo_data/human/demo/",
                help="结果保存目录"
            )
            
            bestmodelpath = st.text_input(
                "模型文件路径 *",
                value="",
                placeholder="例如: deepgp/demo_data/human/demo/train_combine_byBYprocessed/checkpoints/2025-11-08-07-36-59-290604/epoch-150_step-150_mediancos-0.770331.pt",
                help="训练好的模型文件路径。模型通常保存在 数据目录/train_combine_byBYprocessed/checkpoints/[时间戳]/epoch-xxx.pt"
            )
        
        with col2:
            savename = st.text_input(
                "输出文件前缀 *",
                value="demo_prediction",
                help="结果文件名前缀"
            )
            
            ms2_method = st.selectbox(
                "相似度计算方法",
                ["cos_sqrt", "cos", "pcc"],
                index=0,
                help="cos_sqrt: 余弦相似度(平方根) | cos: 余弦相似度 | pcc: 皮尔逊相关系数"
            )
            
            postprocessing = st.selectbox(
                "后处理",
                ["off", "on"],
                index=0,
                help="是否输出详细的片段信息"
            )
            
            device = st.number_input(
                "设备选择",
                min_value=-1,
                max_value=7,
                value=-1,
                help="-1 表示 CPU（推荐，稳定），0-7 表示 GPU 编号"
            )
        
        submitted = st.form_submit_button("🚀 开始预测", use_container_width=True)
    
    if submitted:
        # 验证输入
        if not all([trainpathcsv, datafold, bestmodelpath, savename]):
            st.error("❌ 请填写所有必填字段（标记 * 的字段）")
            return
        
        # 规范化路径
        datafold = normalize_path(datafold)
        
        # 检查文件是否存在
        if not os.path.exists(trainpathcsv):
            st.error(f"❌ 测试数据文件不存在: {trainpathcsv}")
            return
        
        if not os.path.exists(bestmodelpath):
            st.error(f"❌ 模型文件不存在: {bestmodelpath}")
            return
        
        # 构建命令
        cmd = f"""python 3_replace_predict_byBY.py \
            --trainpathcsv {trainpathcsv} \
            --datafold {datafold} \
            --bestmodelpath {bestmodelpath} \
            --savename {savename} \
            --ms2_method {ms2_method} \
            --postprocessing {postprocessing} \
            --device {device}"""
        
        # 执行命令
        success, output = run_command(cmd, "MS/MS 谱图预测")
        
        if success:
            st.markdown("### 📊 预测结果")
            
            result_dir = os.path.join(datafold, "test_replace_predict")
            if os.path.exists(result_dir):
                st.info(f"结果保存在: {result_dir}")
                
                # 列出生成的文件
                result_files = [f for f in os.listdir(result_dir) if savename in f]
                if result_files:
                    st.write("生成的文件:")
                    for f in result_files:
                        st.write(f"  - {f}")
                    
                    # 尝试读取并显示结果摘要
                    for f in result_files:
                        if f.endswith('.csv') and 'score' in f.lower():
                            try:
                                result_path = os.path.join(result_dir, f)
                                df = pd.read_csv(result_path)
                                st.write(f"\n预测得分统计 ({f}):")
                                st.dataframe(df.describe())
                                
                                # 显示分布图
                                if 'score' in df.columns or 'cos' in df.columns:
                                    import matplotlib.pyplot as plt
                                    score_col = 'score' if 'score' in df.columns else [c for c in df.columns if 'cos' in c.lower()][0]
                                    
                                    fig, ax = plt.subplots(figsize=(10, 4))
                                    ax.hist(df[score_col], bins=50, edgecolor='black')
                                    ax.set_xlabel('相似度得分')
                                    ax.set_ylabel('频数')
                                    ax.set_title('预测相似度分布')
                                    st.pyplot(fig)
                            except Exception as e:
                                st.warning(f"⚠️ 无法读取结果文件 {f}: {str(e)}")
    
    # 使用说明
    with st.expander("ℹ️ 使用说明"):
        st.markdown("""
        ### 前置要求
        
        1. 已完成数据预处理
        2. 已下载或训练好模型文件
        
        ### 模型下载
        
        可从以下位置获取预训练模型:
        - [Google Drive](https://drive.google.com/drive/folders/1J4CKnsrikETNgcLj9xL5eRjWqC0oQx8Q)
        - [GitHub Releases](https://github.com/lmsac/DeepGP/releases/)
        
        可用模型:
        - **human**: 人类数据集训练
        - **mouse**: 小鼠数据集训练
        - **human&mouse**: 混合数据集训练
        
        ### 输出文件
        
        预测结果包含:
        - 预测得分 (相似度)
        - 预测的片段离子强度
        - 实验谱图对比
        
        ### 性能参考
        
        - GPU (RTX 3090): ~1秒/40谱图
        - CPU: 约慢 10-50 倍
        
        ### 相似度方法
        
        - **cos_sqrt**: 余弦相似度 + 平方根变换（推荐）
        - **cos**: 标准余弦相似度
        - **pcc**: 皮尔逊相关系数
        """)


def show_rt_calibration():
    """保留时间校准页面"""
    st.markdown('<div class="sub-header">⏱️ 保留时间校准</div>', unsafe_allow_html=True)
    
    st.write("使用 LOWESS 方法校准多个数据集的保留时间")
    
    # 参数输入
    with st.form("rt_calibration_form"):
        st.markdown("### 输入参数")
        
        folder_path = st.text_input(
            "数据目录 *",
            placeholder="/path/to/data/mouse/",
            help="包含所有需要校准的数据文件的目录"
        )
        
        pattern = st.text_input(
            "文件匹配模式 *",
            value="*_rt_1st.csv",
            help="用于匹配数据文件的模式（支持通配符）"
        )
        
        cali_csv = st.text_input(
            "参考文件路径 *",
            placeholder="/path/to/data/mouse/PXD005411/PXD005411_MouseBrain_rt_1st.csv",
            help="用作校准基准的参考文件"
        )
        
        output_name = st.text_input(
            "输出文件名 *",
            value="All_adjust_irt.csv",
            help="校准后合并文件的名称"
        )
        
        submitted = st.form_submit_button("🚀 开始校准", use_container_width=True)
    
    if submitted:
        # 验证输入
        if not all([folder_path, pattern, cali_csv, output_name]):
            st.error("❌ 请填写所有必填字段")
            return
        
        # 规范化路径
        folder_path = normalize_path(folder_path)
        
        # 检查路径
        if not os.path.exists(folder_path):
            st.error(f"❌ 数据目录不存在: {folder_path}")
            return
        
        if not os.path.exists(cali_csv):
            st.error(f"❌ 参考文件不存在: {cali_csv}")
            return
        
        # 构建命令
        cmd = f"""python 1_rt_calibration.py \
            --pattern {pattern} \
            --Cali_csv {cali_csv} \
            --folder_path {folder_path} \
            --output_name {output_name}"""
        
        # 执行命令
        success, output = run_command(cmd, "保留时间校准")
        
        if success:
            st.markdown("### 📊 校准结果")
            
            # 检查输出文件
            output_file = os.path.join(folder_path, output_name)
            if os.path.exists(output_file):
                st.success(f"✅ 校准文件已生成: {output_file}")
                
                # 预览数据
                try:
                    df = pd.read_csv(output_file, nrows=10)
                    st.write("校准后的数据预览:")
                    st.dataframe(df)
                except Exception as e:
                    st.warning(f"⚠️ 无法预览数据: {str(e)}")
            
            # 检查校准图
            plot_file = os.path.join(folder_path, "RT_calibration_plot.pdf")
            if os.path.exists(plot_file):
                st.info(f"📈 校准图已生成: {plot_file}")
                st.write("请查看 PDF 文件以确认校准效果")
            
            # 列出生成的 _adjust.csv 文件
            st.write("\n生成的校准文件:")
            adjust_files = [f for f in os.listdir(folder_path) if f.endswith('_adjust.csv')]
            for f in adjust_files:
                st.write(f"  - {f}")
    
    # 使用说明
    with st.expander("ℹ️ 使用说明"):
        st.markdown("""
        ### 保留时间校准流程
        
        1. **准备数据**
           - 需要多个已预处理的数据集
           - 选择一个质量好的数据集作为参考
        
        2. **校准方法**
           - 使用 LOWESS (Locally Weighted Scatterplot Smoothing)
           - 默认参数: frac=0.2, it=2
        
        3. **输出文件**
           - 每个输入文件生成对应的 `_adjust.csv`
           - 生成合并的校准文件
           - 生成校准图 `RT_calibration_plot.pdf`
        
        ### 注意事项
        
        - 参考文件应选择质量最好、覆盖范围广的数据集
        - 建议查看校准图确认校准效果
        - 如果校准效果不好，可能需要:
          - 更换参考数据集
          - 调整 LOWESS 参数（需修改代码）
          - 检查数据质量
        
        ### 后续步骤
        
        使用校准后的数据进行:
        - 模型训练（用于 iRT 预测）
        - 保留时间预测
        """)


def show_rt_prediction():
    """保留时间预测页面"""
    st.markdown('<div class="sub-header">🎯 保留时间预测</div>', unsafe_allow_html=True)
    
    st.write("预测糖肽的保留时间")
    
    # 参数输入
    with st.form("rt_prediction_form"):
        st.markdown("### 输入参数")
        
        col1, col2 = st.columns(2)
        
        with col1:
            trainpathcsv = st.text_input(
                "测试数据文件 *",
                value="deepgp/demo_data/human/demo/demo_output.csv",
                help="预处理后的数据文件（可以是校准后的，使用第1步的输出）"
            )
            
            datafold = st.text_input(
                "输出目录 *",
                value="deepgp/demo_data/human/demo/",
                help="结果保存目录"
            )
            
            bestmodelpath = st.text_input(
                "模型文件路径 *",
                value="deepgp/model/RT/best_model.pt",
                help="训练好的保留时间预测模型（demo 可用预训练模型）"
            )
        
        with col2:
            savename = st.text_input(
                "输出文件前缀 *",
                value="rt_prediction",
                help="结果文件名前缀"
            )
            
            device = st.number_input(
                "设备选择",
                min_value=-1,
                max_value=7,
                value=-1,
                help="-1 表示 CPU（推荐，稳定），0-7 表示 GPU 编号"
            )
        
        submitted = st.form_submit_button("🚀 开始预测", use_container_width=True)
    
    if submitted:
        # 验证输入
        if not all([trainpathcsv, datafold, bestmodelpath, savename]):
            st.error("❌ 请填写所有必填字段")
            return
        
        # 规范化路径
        datafold = normalize_path(datafold)
        
        # 检查文件
        if not os.path.exists(trainpathcsv):
            st.error(f"❌ 测试数据文件不存在: {trainpathcsv}")
            return
        
        if not os.path.exists(bestmodelpath):
            st.error(f"❌ 模型文件不存在: {bestmodelpath}")
            return
        
        # 构建命令
        cmd = f"""python 3_replace_predict_rt.py \
            --datafold {datafold} \
            --trainpathcsv {trainpathcsv} \
            --bestmodelpath {bestmodelpath} \
            --savename {savename} \
            --device {device}"""
        
        # 执行命令
        success, output = run_command(cmd, "保留时间预测")
        
        if success:
            st.markdown("### 📊 预测结果")
            
            result_dir = os.path.join(datafold, "test_replace_predict")
            if os.path.exists(result_dir):
                st.info(f"结果保存在: {result_dir}")
                
                # 列出结果文件
                result_files = [f for f in os.listdir(result_dir) if savename in f]
                if result_files:
                    st.write("生成的文件:")
                    for f in result_files:
                        st.write(f"  - {f}")
                    
                    # 尝试显示 R² 等统计信息
                    for f in result_files:
                        if f.endswith('.csv'):
                            try:
                                result_path = os.path.join(result_dir, f)
                                df = pd.read_csv(result_path)
                                
                                if 'RT' in df.columns and 'predicted_RT' in df.columns:
                                    # 计算 R²
                                    from sklearn.metrics import r2_score
                                    r2 = r2_score(df['RT'], df['predicted_RT'])
                                    st.success(f"📊 R² Score: {r2:.4f}")
                                    
                                    # 显示散点图
                                    import matplotlib.pyplot as plt
                                    fig, ax = plt.subplots(figsize=(8, 8))
                                    ax.scatter(df['RT'], df['predicted_RT'], alpha=0.5)
                                    ax.plot([df['RT'].min(), df['RT'].max()], 
                                           [df['RT'].min(), df['RT'].max()], 
                                           'r--', lw=2)
                                    ax.set_xlabel('实验保留时间')
                                    ax.set_ylabel('预测保留时间')
                                    ax.set_title(f'保留时间预测 (R² = {r2:.4f})')
                                    st.pyplot(fig)
                                    
                                    # 显示统计
                                    st.write("预测统计:")
                                    st.dataframe(df[['RT', 'predicted_RT']].describe())
                                    
                            except Exception as e:
                                st.warning(f"⚠️ 无法分析结果文件 {f}: {str(e)}")
    
    # 使用说明
    with st.expander("ℹ️ 使用说明"):
        st.markdown("""
        ### 保留时间预测流程
        
        1. **数据预处理** → 2. **保留时间校准** → 3. **模型训练** → 4. **保留时间预测**
        
        ### 模型
        
        - **mouse_rt**: 小鼠保留时间预测模型
        - 可从 [Google Drive](https://drive.google.com/drive/folders/1J4CKnsrikETNgcLj9xL5eRjWqC0oQx8Q) 下载
        
        ### 输入数据
        
        - 可以使用校准后的数据 (`_adjust.csv`)
        - 也可以使用原始预处理数据
        
        ### 评价指标
        
        - **R²**: 决定系数，越接近 1 越好
        - **散点图**: 预测值与实验值的对比
        
        ### 性能
        
        - GPU (RTX 3090): ~38秒/6254谱图
        - 比 MS/MS 预测更快
        """)


def show_ms2_training():
    """MS/MS 谱图预测模型训练"""
    st.markdown('<div class="sub-header">🏋️ MS/MS 模型训练</div>', unsafe_allow_html=True)
    
    st.write("训练 MS/MS 谱图预测模型")
    st.info("💡 **Demo 提示**: 默认值已配置为训练 demo 数据，训练完成后模型保存在 `deepgp/output/` 目录")
    
    with st.form("ms2_training_form"):
        st.markdown("### 训练参数")
        
        col1, col2 = st.columns(2)
        
        with col1:
            task_name = st.text_input(
                "任务名称 *",
                value="demo_ms2_training",
                help="训练任务的名称"
            )
            
            folder_path = st.text_input(
                "数据目录 *",
                value="deepgp/demo_data/human/",
                help="包含训练数据的主目录（注意：实际路径 = 数据目录 + 物种名）"
            )
            
            organism = st.text_input(
                "物种/子目录 *",
                value="demo",
                help="物种或子目录名（将拼接到数据目录后）。例如：demo, human, mouse"
            )
            
            pattern = st.text_input(
                "文件匹配模式 *",
                value="*output.csv",
                help="训练数据文件的匹配模式（使用第1步的输出）"
            )
            
            trainpathcsv = st.text_input(
                "训练数据输出名 *",
                value="train_combine.csv",
                help="合并后的训练数据文件名"
            )
        
        with col2:
            testdata = st.text_input(
                "测试集关键词",
                value="NONE",
                help="包含此关键词的文件将作为测试集（demo数据用NONE，避免文件名冲突）"
            )
            
            ms2_method = st.selectbox(
                "评价指标",
                ["cos_sqrt", "cos", "pcc"],
                index=0,
                help="训练和评估使用的相似度指标"
            )
            
            model_ablation = st.selectbox(
                "模型类型",
                ["BERT", "DeepFLR", "Transformer"],
                index=0,
                help="Demo 推荐 BERT（不需要预训练模型）"
            )
            
            deepflr_path = st.text_input(
                "DeepFLR 基础模型路径",
                value="",
                help="仅 DeepFLR 模式需要（可选，不存在时从头训练）"
            )
            
            lr = st.number_input(
                "学习率",
                value=0.0001,
                format="%.6f",
                help="优化器学习率"
            )
            
            device = st.number_input(
                "设备选择",
                value=-1,
                min_value=-1,
                max_value=7,
                help="-1 表示 CPU（推荐，稳定），0-7 表示 GPU 编号"
            )
        
        submitted = st.form_submit_button("🚀 开始训练", use_container_width=True)
    
    if submitted:
        if not all([task_name, folder_path, organism, pattern, trainpathcsv]):
            st.error("❌ 请填写所有必填字段")
            return
        
        # 规范化路径
        folder_path = normalize_path(folder_path)
        
        # 构建命令
        cmd = f"""python 2_train_byBY.py \
            --task_name {task_name} \
            --folder_path {folder_path} \
            --organism {organism} \
            --pattern {pattern} \
            --trainpathcsv {trainpathcsv} \
            --testdata {testdata} \
            --ms2_method {ms2_method} \
            --model_ablation {model_ablation}"""
        
        # 只有当提供了 DeepFLR_modelpath 时才添加
        if deepflr_path:
            cmd += f" \\\n            --DeepFLR_modelpath {deepflr_path}"
        
        cmd += f""" \
            --lr {lr} \
            --device {device}"""
        
        st.info("⏳ 训练可能需要很长时间，建议在命令行或后台运行")
        
        st.markdown("### 📋 命令行运行方式")
        st.markdown("**在服务器上执行以下命令（从项目根目录）：**")
        
        # 显示完整的命令（包含 cd 和 deepgp/ 前缀）
        full_cmd = f"""cd /home/gaoyu/deepgp/DeepGP
conda activate deepgp
python deepgp/2_train_byBY.py \\
    --task_name {task_name} \\
    --folder_path {folder_path} \\
    --organism {organism} \\
    --pattern "{pattern}" \\
    --trainpathcsv {trainpathcsv} \\
    --testdata {testdata} \\
    --ms2_method {ms2_method} \\
    --model_ablation {model_ablation}"""
        
        # 只有当提供了 DeepFLR_modelpath 时才添加
        if deepflr_path:
            full_cmd += f" \\\n    --DeepFLR_modelpath {deepflr_path}"
        
        full_cmd += f""" \\
    --lr {lr} \\
    --device {device}"""
        
        # 提示实际数据路径
        actual_data_path = folder_path.rstrip('/') + '/' + organism + '/'
        st.info(f"📂 实际数据路径：`{actual_data_path}`")
        
        st.code(full_cmd, language="bash")
        
        if st.button("确认在 Streamlit 中开始训练"):
            success, output = run_command(cmd, "模型训练")


def show_rt_training():
    """保留时间预测模型训练"""
    st.markdown('<div class="sub-header">🏋️ 保留时间模型训练</div>', unsafe_allow_html=True)
    
    st.write("训练保留时间预测模型")
    st.info("💡 **Demo 提示**: 默认值已配置为训练 demo 数据，训练完成后模型保存在 `deepgp/output/` 目录")
    
    with st.form("rt_training_form"):
        st.markdown("### 训练参数")
        
        col1, col2 = st.columns(2)
        
        with col1:
            task_name = st.text_input(
                "任务名称 *",
                value="demo_rt_training",
                help="训练任务的名称"
            )
            
            folder_path = st.text_input(
                "数据目录 *",
                value="deepgp/demo_data/human/demo/",
                help="包含训练数据的主目录"
            )
            
            pattern = st.text_input(
                "文件匹配模式 *",
                value="*output.csv",
                help="训练数据文件的匹配模式（使用第1步的输出）"
            )
            
            trainpathcsv = st.text_input(
                "训练数据输出名 *",
                value="train_rt_combine.csv",
                help="合并后的训练数据文件名"
            )
        
        with col2:
            irt = st.selectbox(
                "是否使用 iRT 校准",
                ["no", "yes"],
                index=0,
                help="是否使用校准后的保留时间（demo 选 no）"
            )
            
            irt_csv = st.text_input(
                "iRT 校准文件",
                value="",
                help="如果使用 iRT，指定校准文件路径"
            )
            
            testdata = st.text_input(
                "测试集关键词",
                value="NONE",
                help="包含此关键词的文件将作为测试集（demo数据用NONE，避免文件名冲突）"
            )
            
            deepflr_path = st.text_input(
                "DeepFLR 基础模型路径 *",
                value="deepgp/model/DeepFLR/best_model",
                help="DeepFLR 预训练模型路径（必需）"
            )
            
            lr = st.number_input(
                "学习率",
                value=0.0001,
                format="%.6f",
                help="优化器学习率"
            )
            
            device = st.number_input(
                "设备选择",
                value=-1,
                min_value=-1,
                max_value=7,
                help="-1 表示 CPU（推荐，稳定），0-7 表示 GPU 编号"
            )
        
        submitted = st.form_submit_button("🚀 开始训练", use_container_width=True)
    
    if submitted:
        if not all([task_name, folder_path, pattern, trainpathcsv]):
            st.error("❌ 请填写所有必填字段")
            return
        
        if irt == "yes" and not irt_csv:
            st.error("❌ 使用 iRT 时必须指定校准文件")
            return
        
        # 规范化路径
        folder_path = normalize_path(folder_path)
        
        # 构建命令
        cmd = f"""python 2_train_rt.py \
            --irt {irt} \
            --task_name {task_name} \
            --folder_path {folder_path} \
            --pattern {pattern} \
            --trainpathcsv {trainpathcsv} \
            --testdata {testdata} \
            --model_ablation DeepFLR"""
        
        # 只有当提供了 DeepFLR_modelpath 时才添加
        if deepflr_path:
            cmd += f" \\\n            --DeepFLR_modelpath {deepflr_path}"
        
        cmd += f""" \
            --lr {lr} \
            --device {device}"""
        
        if irt == "yes":
            cmd += f" --irt_csv {irt_csv}"
        
        st.info("⏳ 训练可能需要很长时间，建议在命令行或后台运行")
        
        st.markdown("### 📋 命令行运行方式")
        st.markdown("**在服务器上执行以下命令（从项目根目录）：**")
        
        # 显示完整的命令
        full_cmd = f"""cd /home/gaoyu/deepgp/DeepGP
conda activate deepgp
python deepgp/2_train_rt.py \\
    --irt {irt} \\
    --task_name {task_name} \\
    --folder_path {folder_path} \\
    --pattern "{pattern}" \\
    --trainpathcsv {trainpathcsv} \\
    --testdata {testdata} \\
    --model_ablation DeepFLR"""
        
        # 只有当提供了 DeepFLR_modelpath 时才添加
        if deepflr_path:
            full_cmd += f" \\\n    --DeepFLR_modelpath {deepflr_path}"
        
        full_cmd += f""" \\
    --lr {lr} \\
    --device {device}"""
        
        if irt == "yes":
            full_cmd += f" \\\n    --irt_csv {irt_csv}"
        
        st.code(full_cmd, language="bash")
        
        if st.button("确认在 Streamlit 中开始训练"):
            success, output = run_command(cmd, "保留时间模型训练")


def show_results():
    """结果查看页面"""
    st.markdown('<div class="sub-header">📈 结果查看</div>', unsafe_allow_html=True)
    
    st.write("查看和分析预测结果")
    
    # 选择结果目录
    result_dir = st.text_input(
        "结果目录",
        placeholder="/path/to/data/demo/test_replace_predict/",
        help="包含预测结果的目录"
    )
    
    if result_dir and os.path.exists(result_dir):
        # 列出所有结果文件
        files = [f for f in os.listdir(result_dir) if f.endswith('.csv') or f.endswith('.json')]
        
        if files:
            selected_file = st.selectbox("选择结果文件", files)
            
            if selected_file:
                file_path = os.path.join(result_dir, selected_file)
                
                try:
                    if selected_file.endswith('.csv'):
                        df = pd.read_csv(file_path)
                        
                        st.markdown("### 📊 结果统计")
                        
                        col1, col2, col3 = st.columns(3)
                        with col1:
                            st.metric("总行数", len(df))
                        with col2:
                            st.metric("列数", len(df.columns))
                        with col3:
                            if 'iden_pep' in df.columns:
                                st.metric("唯一糖肽", df['iden_pep'].nunique())
                        
                        # 显示数据摘要
                        st.markdown("### 📋 数据摘要")
                        st.dataframe(df.describe())
                        
                        # 显示前几行
                        st.markdown("### 👀 数据预览")
                        n_rows = st.slider("显示行数", 5, 100, 10)
                        st.dataframe(df.head(n_rows))
                        
                        # 如果有相似度列，显示分布
                        score_cols = [c for c in df.columns if 'cos' in c.lower() or 'score' in c.lower() or 'r2' in c.lower()]
                        if score_cols:
                            st.markdown("### 📈 相似度分布")
                            
                            for col in score_cols:
                                st.write(f"**{col}**")
                                
                                col1, col2 = st.columns([2, 1])
                                
                                with col1:
                                    import matplotlib.pyplot as plt
                                    fig, ax = plt.subplots(figsize=(10, 4))
                                    ax.hist(df[col].dropna(), bins=50, edgecolor='black')
                                    ax.set_xlabel(col)
                                    ax.set_ylabel('频数')
                                    ax.set_title(f'{col} 分布')
                                    st.pyplot(fig)
                                
                                with col2:
                                    st.write("统计:")
                                    st.write(f"均值: {df[col].mean():.4f}")
                                    st.write(f"中位数: {df[col].median():.4f}")
                                    st.write(f"标准差: {df[col].std():.4f}")
                                    st.write(f"最小值: {df[col].min():.4f}")
                                    st.write(f"最大值: {df[col].max():.4f}")
                        
                        # 下载按钮
                        st.markdown("### 💾 下载结果")
                        st.download_button(
                            label="下载 CSV 文件",
                            data=df.to_csv(index=False).encode('utf-8'),
                            file_name=selected_file,
                            mime='text/csv'
                        )
                    
                    elif selected_file.endswith('.json'):
                        with open(file_path, 'r') as f:
                            data = json.load(f)
                        
                        st.json(data)
                        
                        # 下载按钮
                        st.download_button(
                            label="下载 JSON 文件",
                            data=json.dumps(data, indent=2),
                            file_name=selected_file,
                            mime='application/json'
                        )
                
                except Exception as e:
                    st.error(f"❌ 无法读取文件: {str(e)}")
        else:
            st.warning("⚠️ 该目录中没有结果文件")
    elif result_dir:
        st.error(f"❌ 目录不存在: {result_dir}")
    else:
        st.info("👆 请输入结果目录路径")


def show_help():
    """帮助文档页面"""
    st.markdown('<div class="sub-header">ℹ️ 帮助文档</div>', unsafe_allow_html=True)
    
    tab1, tab2, tab3, tab4 = st.tabs(["📖 快速开始", "🔧 常见问题", "📚 API 文档", "📞 联系我们"])
    
    with tab1:
        st.markdown("""
        ## 快速开始指南
        
        ### 基本工作流程
        
        #### 1. 数据预处理
        - 准备 pGlyco3 鉴定结果和 MGF 文件
        - 在"数据预处理"页面处理数据
        
        #### 2. MS/MS 谱图预测
        - 下载预训练模型或训练自己的模型
        - 在"MS/MS 谱图预测"页面进行预测
        
        #### 3. 保留时间分析（可选）
        - 校准多个数据集的保留时间
        - 训练或使用保留时间预测模型
        
        #### 4. 结果分析
        - 在"结果查看"页面查看和下载结果
        
        ### 示例数据
        
        项目包含演示数据，位于 `demo_data/human/demo/`
        
        ### 预训练模型
        
        从以下位置下载:
        - [Google Drive](https://drive.google.com/drive/folders/1J4CKnsrikETNgcLj9xL5eRjWqC0oQx8Q)
        - [GitHub Releases](https://github.com/lmsac/DeepGP/releases/)
        
        可用模型:
        - **DeepFLR**: 基础模型（必需）
        - **human**: 人类谱图预测
        - **mouse**: 小鼠谱图预测
        - **human&mouse**: 混合谱图预测
        - **mouse_rt**: 小鼠保留时间预测
        """)
    
    with tab2:
        st.markdown("""
        ## 常见问题
        
        ### Q: 如何安装依赖？
        
        ```bash
        pip install -r requirements.txt
        ```
        
        ### Q: GPU 不可用怎么办？
        
        - 可以使用 CPU，但速度较慢
        - 在设备编号处输入 -1 使用 CPU
        - 或者安装正确的 CUDA 和 PyTorch GPU 版本
        
        ### Q: 内存不足怎么办？
        
        - 减小 batch size（在 utils.py 中）
        - 使用更小的数据集
        - 关闭后处理选项
        
        ### Q: 预测结果不理想？
        
        - 确认使用了正确的模型（human/mouse）
        - 检查数据预处理是否正确
        - 尝试不同的相似度计算方法
        - 考虑使用自己的数据训练模型
        
        ### Q: 如何训练自己的模型？
        
        - 准备足够的训练数据（建议 1000+ 条谱图）
        - 下载 DeepFLR 基础模型
        - 在"模型训练"页面配置参数
        - 训练可能需要数小时到数天
        
        ### Q: 文件路径应该用什么格式？
        
        - 建议使用绝对路径
        - Linux/Mac: `/home/user/data/`
        - Windows: `C:/Users/user/data/` 或 `C:\\Users\\user\\data\\`
        
        ### Q: 支持哪些数据格式？
        
        - **输入**: pGlyco3 结果（.txt）和 MGF 文件
        - **输出**: CSV 和 JSON 格式
        - MGF 可以来自 MsConvert 或 pGlyco3
        
        ### Q: 如何批量处理多个数据集？
        
        - 使用文件匹配模式（如 `*_data_1st.csv`）
        - 或编写脚本循环处理
        - 参考快速开始文档中的批处理示例
        """)
    
    with tab3:
        st.markdown("""
        ## 命令行 API
        
        ### 数据预处理
        
        ```bash
        python 1_dataset_format.py \\
            --datafold <数据目录> \\
            --dfname <鉴定结果文件名> \\
            --mgfdatafold <MGF文件夹名> \\
            --output_name <输出文件名> \\
            --dup <重复处理方式> \\
            --mgfsourceorign <MGF来源>
        ```
        
        ### MS/MS 谱图预测
        
        ```bash
        python 3_replace_predict_byBY.py \\
            --trainpathcsv <测试数据文件> \\
            --datafold <输出目录> \\
            --bestmodelpath <模型文件> \\
            --savename <输出前缀> \\
            --ms2_method <相似度方法> \\
            --postprocessing <on/off> \\
            --device <GPU编号>
        ```
        
        ### 保留时间校准
        
        ```bash
        python 1_rt_calibration.py \\
            --pattern <文件匹配模式> \\
            --Cali_csv <参考文件> \\
            --folder_path <数据目录> \\
            --output_name <输出文件名>
        ```
        
        ### 保留时间预测
        
        ```bash
        python 3_replace_predict_rt.py \\
            --trainpathcsv <测试数据文件> \\
            --datafold <输出目录> \\
            --bestmodelpath <模型文件> \\
            --savename <输出前缀> \\
            --device <GPU编号>
        ```
        
        ### 模型训练 (MS/MS)
        
        ```bash
        python 2_train_byBY.py \\
            --task_name <任务名称> \\
            --folder_path <数据目录> \\
            --organism <物种> \\
            --pattern <文件匹配模式> \\
            --trainpathcsv <训练数据输出名> \\
            --ms2_method <评价指标> \\
            --model_ablation <模型类型> \\
            --DeepFLR_modelpath <DeepFLR路径> \\
            --lr <学习率> \\
            --device <GPU编号>
        ```
        
        ### 模型训练 (保留时间)
        
        ```bash
        python 2_train_rt.py \\
            --irt <yes/no> \\
            --irt_csv <校准文件> \\
            --task_name <任务名称> \\
            --folder_path <数据目录> \\
            --pattern <文件匹配模式> \\
            --trainpathcsv <训练数据输出名> \\
            --testdata <测试集关键词> \\
            --DeepFLR_modelpath <DeepFLR路径> \\
            --lr <学习率> \\
            --device <GPU编号>
        ```
        """)
    
    with tab4:
        st.markdown("""
        ## 联系我们
        
        ### 获取帮助
        
        如果遇到问题或有建议，可以通过以下方式联系:
        
        - 📧 **Email**: liang_qiao@fudan.edu.cn
        - 🐛 **GitHub Issues**: [https://github.com/lmsac/DeepGP/issues](https://github.com/lmsac/DeepGP/issues)
        - 📚 **GitHub**: [https://github.com/lmsac/DeepGP](https://github.com/lmsac/DeepGP)
        
        ### 反馈
        
        我们欢迎任何反馈和建议！请告诉我们:
        - 发现的 bug
        - 功能请求
        - 使用体验
        - 文档改进建议
        
        ### 引用
        
        如果您在研究中使用了 DeepGP，请引用相关论文。
        
        ### 许可证
        
        DeepGP 采用 BSD-2-Clause 许可证。
        
        ### 贡献
        
        欢迎贡献代码！请查看 GitHub 仓库了解贡献指南。
        
        ---
        
        **感谢使用 DeepGP！** 🎉
        """)


if __name__ == "__main__":
    main()

